﻿namespace com.stifel
{
    using System;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;

    [Serializable]
    public class RomanNumeral : IDisposable
    {
        #region Fields

        static string[,] values = 
            {
                { "M", "1000" },
                { "CM", "900" },
                { "D", "500" },
                { "CD", "400" },
                { "C", "100" },
                { "XC", "90" },
                { "L", "50" },
                { "XL", "40" },
                { "X", "10" },
                { "IX", "9" },
                { "V", "5" },
                { "IV", "4" },
                { "I", "1" }
            };

        bool disposed;

        #endregion Fields

        #region Constructors

        public RomanNumeral( DateTime value )
            : this(value.Year)
        {
        }

        public RomanNumeral( string value )
            : this(toInt64( value ))
        {
        }

        public RomanNumeral( Int16 int16 )
            : this(Convert.ToInt64( int16 ))
        {
        }

        public RomanNumeral( Int32 int32 )
            : this(Convert.ToInt64( int32 ))
        {
        }

        public RomanNumeral( Int64 int64 )
        {
            NumericValue = int64;
            Value = toRomanNumeral( int64 );
        }

        #endregion Constructors

        #region Properties

        public Int64 NumericValue
        {
            get;
            private set;
        }

        public string Value
        {
            get;
            private set;
        }

        #endregion Properties

        #region Methods

        public static bool IsRomanNumeral( string value )
        {
            Int64 i = toInt64( value );
            return i > 0;
        }

        /// <summary>
        /// This IDisposable implementation follows Microsoft coding best practices.
        /// Dispose is also initiated by the destructor of this class.
        /// Performs tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// Because the Dispose is implemented, this class can be used in a 'using' statement.
        /// Do not make this method virtual. A derived class should not be able to override this method.
        /// Note: Within the destructor use Dispose(false). 
        /// </summary>
        public void Dispose( )
        {
            Dispose( true );
            // This object will be cleaned up by the Dispose method.
            // Therefore, call GC.SuppressFinalize to take this object off the finalization queue
            // and prevent finalization code for this object from executing a second time.
            GC.SuppressFinalize( this );
        }

        public override string ToString( )
        {
            return Value;
        }

        static string adjust( string value )
        {
            const string chars = "MDCLXVI";
            StringBuilder sb = new StringBuilder( );
            foreach ( char c in value.ToUpper( ).ToCharArray( ) )
                if ( chars.Contains( c ) )
                    sb.Append( c );
            string s = sb.ToString( );
            sb = null;
            return s;
        }

        static int indexOf( string value )
        {
            for ( int x = 0; x <= values.GetUpperBound( 0 ); x++ )
            {
                if ( values [ x, 0 ] == value )
                {
                    return Convert.ToInt32( x );
                }
            }

            return -1;
        }

        static Int64 toInt64( string value )
        {
            //string regex = "(^M{0,4})(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$";
            value = adjust( value );
            string regex = @"(^		# beginning of string
                M{0,4})				# thousands - 0 to 4 M's
                (CM|CD|D?C{0,3})	# hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
                                    # or 500-800 (D, followed by 0 to 3 C's)
                (XC|XL|L?X{0,3})	# tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
                                    # or 50-80 (L, followed by 0 to 3 X's)
                (IX|IV|V?I{0,3})	# ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
                                    # or 5-8 (V, followed by 0 to 3 I's)
                $					# end of string";

            RegexOptions options =
                ( ( RegexOptions.IgnorePatternWhitespace |
                RegexOptions.Multiline ) |
                RegexOptions.IgnoreCase );

            Regex reg = new Regex( regex, options );

            string [ ] rn = reg.Split( value.ToUpper( ) );

            Int64 y = 0;
            foreach ( string s in rn )
            {
                if ( s != string.Empty )
                {
                    y += valueOf( s );
                }
            }

            return y;
        }

        static int valueOf( string value )
        {
            int x = indexOf( value );
            if ( x > -1 )
            {
                return Convert.ToInt32( values [ x, 1 ] );
            }
            else
                return 0;
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        void Dispose( bool disposing )
        {
            // check to see if Dispose has already been called.
            if ( !this.disposed )
            {
                // if displosing equals true, dispose all managed and unmanaged resources
                if ( disposing )
                {
                    // clear managed resources
                }
                disposed = true;
            }
        }

        string toRomanNumeral( Int64 a )
        {
            Int64 i = a;
            string s = string.Empty;
            for ( int x = 0; x <= values.GetUpperBound( 0 ); x++ )
                while ( i >= Convert.ToInt32( values [ x, 1 ] ) )
                {
                    s = string.Concat( s, values [ x, 0 ] );
                    i -= Convert.ToInt32( values [ x, 1 ] );
                }
            return s;
        }

        #endregion Methods
    }
}